package utils

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
)

// 定义常量
const (
	typeUKN          = iota // UnKnow type
	typeBMP                 // bmp ico
	typePNG                 // png ico
	fileHeaderSize   = 6    // 文件头的大小
	headerSize       = 16   // icon图标的头结构大小
	bitmapHeaderSize = 14   // 位图文件头
	dibHeaderSize    = 40   // dib结构头
)

// 定义变量
var (
	// 错误信息
	ErrIcoInvalid  = errors.New("ico: Invalid icon file")                  // 无效的ico文件
	ErrIcoReaders  = errors.New("ico: Reader type is not os.File pointer") // LoadIconFile的io.Reader参数不是文件指针
	ErrIcoFileType = errors.New("ico: Reader is directory, not file")      // io.Reader的文件指针是目录，不是文件
	ErrIconsIndex  = errors.New("ico: Slice out of bounds")                // 读取ico文件时，可能出现的切片越界错误
)

// 类型定义
// 定义icon图标数据的存放
type (
	ICONTYPE      int
	WinIconData   [][]byte
	WinIconStruct []winIconStruct
)

// 定义 Windows 系统的 Ico 文件结构
type WinIcon struct {
	fileHeader *winIconFileHeader // 文件头
	icos       WinIconStruct      // icon 头结构
	icod       WinIconData        // 单独的icon图标的数据
	data       []byte             // 所有ico文件数据
}

type winIconFileHeader struct {
	ReservedA  uint16 // 保留字段，始终为 '0x0000'
	FileType   uint16 // 图像类型：'0x0100' 为 ico，'0x0200' 为 cur
	ImageCount uint16 // 图像数量：至少为 '0x0100' 即 1个图标
}

type winIconStruct struct {
	Width         uint8  // 图像宽度
	Height        uint8  // 图像高度
	Palette       uint8  // 调色板颜色数，不使用调色版为 '0x00'
	ReservedB     uint8  // 保留字段，始终为 '0x00'
	ColorPlanes   uint16 // 在ico中，指定颜色平面，'0x0000' 或则 '0x0100'
	BitsPerPixel  uint16 // 在ico中，指定每像素的位数，如：'0x2000' 32bit
	ImageDataSize uint32 // 图像数据的大小，单位字节
	ImageOffset   uint32 // 图像数据的偏移量
}

type dibHeader struct {
	dibSize        uint32 // 4bytes
	bitmapWidth    uint32 // 4bytes
	bitmapHeight   uint32 // 4bytes
	colorPlanes    uint16 // 2bytes
	BitsPerPixel   uint16 // 2bytes
	markerBI_RGB   uint32 // 4bytes
	originalSize   uint32 // 4bytes
	printResH      uint32 // 4bytes
	printResV      uint32 // 4bytes
	Palette        uint32 // 4bytes
	importantColor uint32 // 4bytes
}

type bitmapHeader struct {
	bitmapID         uint16
	fileSize         uint32
	unusedA          uint16
	unusedB          uint16
	bitmapDataOffset uint32
}

// createBitmapHeader 创建位图文件头结构
func createBitmapHeader(datasize int) *bitmapHeader {
	return &bitmapHeader{
		bitmapID:         binary.LittleEndian.Uint16([]byte{0x42, 0x4d}),
		fileSize:         uint32(datasize + bitmapHeaderSize),
		unusedA:          0,
		unusedB:          0,
		bitmapDataOffset: uint32(bitmapHeaderSize + dibHeaderSize),
	}
}

// GetIconType 获取icon的数据类型
func GetIconType(d []byte) ICONTYPE {
	if checkDIBHeader(d) {
		return typeBMP
	}
	if checkPNGHeader(d) {
		return typePNG
	} else {
		return typeUKN
	}
}

// checkPNGHeader 检测是否是png ico数据
func checkPNGHeader(d []byte) bool {
	if len(d) < 8 {
		return false
	}
	if bytes.Compare(d[0:8], []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}) != 0 {
		return false
	}
	return true
}

// headerToBytes 将bitmapHeader位图头结构转换为字节切片
func (bmh *bitmapHeader) headerToBytes() []byte {
	d := make([]byte, bitmapHeaderSize)
	binary.LittleEndian.PutUint16(d[0:2], bmh.bitmapID)
	binary.LittleEndian.PutUint32(d[2:6], bmh.fileSize)
	binary.LittleEndian.PutUint16(d[6:8], bmh.unusedA)
	binary.LittleEndian.PutUint16(d[8:10], bmh.unusedB)
	binary.LittleEndian.PutUint32(d[10:14], bmh.bitmapDataOffset)
	return d
}

// JoinHeader 将bitmapfileheader链接到含有dib头的位图数据前
func (bmh *bitmapHeader) JoinHeader(d []byte) []byte {
	h := bmh.headerToBytes()
	j := [][]byte{h, d}
	return bytes.Join(j, nil)
}

// checkDIBHeader 检测是否是bmp ico数据
func checkDIBHeader(d []byte) bool {
	if len(d) < 40 {
		return false
	}
	a := d[0:4]
	b := []byte{0x28, 0, 0, 0}
	if bytes.Compare(a, b) != 0 {
		return false
	}
	return true
}

// 将ico文件的数据载入到内存
func LoadIconFile(rd io.Reader) (icon *WinIcon, err error) {
	// 类型断言
	v, t := rd.(*os.File)
	if !t {
		return nil, ErrIcoReaders
	}
	// 声明与定义变量
	var (
		fileSize int64
		ico      *WinIcon
	)

	// 获取文件信息及判断是否是文件，而不是目录
	fi, err := v.Stat()
	if err != nil {
		return nil, err
	}
	if fi.IsDir() {
		return nil, ErrIcoFileType
	}
	fileSize = fi.Size()

	// 创建缓冲IO的Reader对象窥视6个字节的文件头
	reader := bufio.NewReader(rd)
	p, err := reader.Peek(fileHeaderSize)
	if err != nil {
		return nil, err
	}

	// 检测文件头及获取头结构
	icoHeader, err := getIconFileHeader(p)
	if err != nil {
		return nil, err
	}

	// 获取ico文件的所有数据
	data, err := getFileAll(reader, fileSize)
	if err != nil {
		return nil, err
	}

	// 创建一个 winIconStruct 数组切片
	icos := make(WinIconStruct, int(icoHeader.ImageCount))
	icod := make(WinIconData, int(icoHeader.ImageCount))
	// 根据文件头中表示的icon图标文件的数量进行循环
	structOffset := fileHeaderSize
	for i := 0; i < int(icoHeader.ImageCount); i++ {
		wis := getIconStruct(data, structOffset, headerSize)
		icodata := wis.getImageData(data, wis.getIconOffset(), wis.getIconLength())
		structOffset += headerSize
		icos[i] = *wis
		icod[i] = icodata
	}

	// 创建 WinIcon 对象
	ico = &WinIcon{
		fileHeader: icoHeader,
		icos:       icos,
		icod:       icod,
		data:       data,
	}
	return ico, nil
}

// getFileAll 获取ico文件所有数据(不包括文件头的6个字节)
// rd *bufio.Reader: 对象
// size int64: 文件大小（我们需要读取的总数量）
// fb []byte: 文件的所有数据，如果成功读取的话
// err error: 如果读取出现错误，返回错误
func getFileAll(rd *bufio.Reader, size int64) (fb []byte, err error) {
	data := make([]byte, size)
	for i := int64(0); i < size; i++ {
		b, err := rd.ReadByte()
		if err != nil {
			return nil, err
		}
		data[i] = b
	}
	return data, nil
}

// getIconFileHeader 获取文件头结构
// b []byte: 读取的数据来自这个字节切片
// wih *winIconFileHeader: 如果获取成功返回 winIconFileHeader对象指针
// err error: 如果读取发生错误，则返回错误信息
func getIconFileHeader(b []byte) (wih *winIconFileHeader, err error) {
	if len(b) != fileHeaderSize {
		return nil, ErrIcoInvalid
	}
	reserved := binary.LittleEndian.Uint16(b[0:2])
	filetype := binary.LittleEndian.Uint16(b[2:4])
	imagecount := binary.LittleEndian.Uint16(b[4:6])
	if reserved != 0 || filetype != 1 || imagecount == 0 {
		return nil, ErrIcoInvalid
	}
	header := &winIconFileHeader{
		ReservedA:  reserved,
		FileType:   filetype,
		ImageCount: imagecount,
	}
	return header, nil
}

// getIconStruct 根据 offset, length 来获取icon图标结构
// b []byte: 文件数据的字节切片
// offset int: 偏移量
// length int: 数据长度
func getIconStruct(b []byte, offset, length int) (wis *winIconStruct) {
	var std []byte
	//std = b[offset:length]
	std = make([]byte, headerSize)
	j := 0
	for i := offset; i < length+offset; i++ {
		std[j] = b[i]
		j++
	}
	is := &winIconStruct{
		Width:         std[0],
		Height:        std[1],
		Palette:       std[2],
		ReservedB:     std[3],
		ColorPlanes:   binary.LittleEndian.Uint16(std[4:6]),
		BitsPerPixel:  binary.LittleEndian.Uint16(std[6:8]),
		ImageDataSize: binary.LittleEndian.Uint32(std[8:12]),
		ImageOffset:   binary.LittleEndian.Uint32(std[12:]),
	}
	return is
}

// getImageData 根据 offset, length 参数获取图标图像数据
// data []byte: 图像数据的字节切片
// offset int: 图像数据的偏移量
// length int: 图像数据的长度
// return []byte: 返回获取的数据字节切片
func (wi *WinIcon) getImageData(data []byte, offset, datasize int) []byte {
	var d = make([]byte, datasize) //data[offset:length]
	for i, j := offset, 0; i < datasize+offset; i++ {
		d[j] = data[i]
		j++
	}
	return d
}

func (wis winIconStruct) getImageData(data []byte, offset, ds int) []byte {
	var d = make([]byte, ds)
	for i, j := offset, 0; i < ds+offset; i++ {
		d[j] = data[i]
		j++
	}
	return d
}

// ExtractIconToFile 提取 ico 数据到文件
// filePrefix string: 为前缀，如果传如空字符串，则没有前缀，使用数字和分辨率作为文件名
// filePath string: 提取的数据写入的路径，空字符串则将文件保存到当前目录
// 舍弃：--count int: 提取文件的数量，0: 为所有，> 0 则根据已保存的map对象来提取对应数量内容，指定数量超出实际数量则全部提取--
// 该函数不检测路径的有效性，使用者自己把控，如果路径有问题，会返回error对象
func (wi *WinIcon) ExtractIconToFile(filePrefix, filePath string) error {
	var ext string
	for i, v := range wi.icos {
		w := v.getIconWidth()
		h := v.getIconHeight()
		b := v.getIconBitsPerPixel()
		d, _ := wi.GetImageData(i)
		if GetIconType(d) == typeBMP {
			ext = "bmp"
		} else {
			ext = "png"
			if w == 0 && h == 0 {
				w = 256
				h = 256
			}
		}
		fn := v.generateFileNameFormat(filePrefix, ext, w, h, b)
		if err := wi.IconToFile(filePath, fn, i); err != nil {
			return err
		}
	}
	return nil
}

// GetImageData 获取ico图标的图像数据
// index int: 下标索引，0序
// 如果越界或读取数据错误，返回 error 对象
func (wi *WinIcon) GetImageData(index int) (d []byte, err error) {
	if index >= wi.getIconsHeaderCount() || index < 0 {
		return nil, ErrIconsIndex
	}
	wis := wi.icos[index]
	offset := wis.getIconOffset()
	datasize := wis.getIconLength()
	data := wi.getImageData(wi.data, offset, datasize)
	return data, nil
}

// IconToFile 将图标写入文件
// path string: 文件写入的路径
// name string: 文件名
// error 如果写入发生错误，则返回错误信息
// IconToFile 并不会检测路径是否有效
func (wi *WinIcon) IconToFile(path, name string, index int) error {
	if index >= wi.getIconsHeaderCount() || index < 0 {
		return ErrIconsIndex
	}
	wis := wi.icos[index]
	p := filepath.Join(path, name)
	d, e := wi.GetImageData(index)
	if e != nil {
		return e
	}
	// 处理bitmap头结构
	if GetIconType(d) == typeBMP {
		w := wis.getIconWidth()
		h := wis.getIconHeight()
		b := wis.getIconBitsPerPixel()
		s := len(d) - dibHeaderSize
		dib := createDIBHeader(w, h, b, s, 0, 0)
		err := dib.EditDIBHeader(d)
		if err != nil {
			return err
		}
		bmh := createBitmapHeader(len(d))
		d = bmh.JoinHeader(d)
	}
	if e := wis.IconToFile(p, d); e != nil {
		return e
	} else {
		return nil
	}
}

// IconToIcoFile 将ico文件中的指定icon图标数据写入ico文件
// path string: 路径（不检查合法性）
// index int: icon图标的索引
// error: 如果发生错误返回error对象
func (wi *WinIcon) IconToIcoFile(path string, index int) error {
	if index < 0 || index >= len(wi.icos) {
		return ErrIconsIndex
	}
	d := wi.icod[index]
	wis := wi.icos[index]
	wis.ImageOffset = fileHeaderSize + headerSize
	wis.ImageDataSize = uint32(len(d))
	d = wis.joinHeader(d)
	if e := ioutil.WriteFile(path, d, getPerm()); e != nil {
		return e
	}
	return nil
}

// getIconsHeaderCount 获取 icons 图标的结构数量-可能和头结构的ico数量不一致，只是可能
// 返回值为数量，类型 int
func (wi *WinIcon) getIconsHeaderCount() int {
	return len(wi.icos)
}

// generateFileNameFormat 产生文件名
func (wis winIconStruct) generateFileNameFormat(prefix, ext string, width, height, bit int) string {
	return fmt.Sprintf("%s_icon%dx%d@%dbit.%s", prefix, width, height, bit, ext)
}

// iconToFile 将ico图像数据写入磁盘文件
func (wis winIconStruct) IconToFile(path string, data []byte) error {
	if err := ioutil.WriteFile(path, data, getPerm()); err != nil {
		return err
	}
	return nil
}

func (wis winIconStruct) headerToBytes() []byte {
	d := make([]byte, fileHeaderSize+headerSize)
	binary.LittleEndian.PutUint16(d[0:2], 0)
	binary.LittleEndian.PutUint16(d[2:4], 1)
	binary.LittleEndian.PutUint16(d[4:6], 1)
	d[6] = uint8(wis.getIconWidth())
	d[7] = uint8(wis.getIconHeight())
	d[8] = wis.Palette
	d[9] = wis.ReservedB
	binary.LittleEndian.PutUint16(d[10:12], wis.ColorPlanes)
	binary.LittleEndian.PutUint16(d[12:14], wis.BitsPerPixel)
	binary.LittleEndian.PutUint32(d[14:18], wis.ImageDataSize)
	binary.LittleEndian.PutUint32(d[18:22], wis.ImageOffset)
	return d
}

func (wis winIconStruct) joinHeader(d []byte) []byte {
	h := wis.headerToBytes()
	j := [][]byte{h, d}
	return bytes.Join(j, nil)
}

// getIconOffset 获取icon图像数据的偏移量
// 返回偏移量数据
func (wis winIconStruct) getIconOffset() int {
	return int(wis.ImageOffset)
}

// getIconLength 获取icon图像数据的长度
// 返回长度数据
func (wis winIconStruct) getIconLength() int {
	return int(wis.ImageDataSize)
}

// getIconWidth 获取icon图像数据的宽度
func (wis winIconStruct) getIconWidth() int {
	return int(wis.Width)
}

// getIconHeight 获取icon图像数据的高度
func (wis winIconStruct) getIconHeight() int {
	return int(wis.Height)
}

// getIconBitsPerPixel 获取icon图像数据的颜色位数
func (wis winIconStruct) getIconBitsPerPixel() int {
	return int(wis.BitsPerPixel)
}

// createDIBHeader创建DIB头结构
func createDIBHeader(width, height, bit, size, p, i int) *dibHeader {
	return &dibHeader{
		dibSize:        40,             // uint32
		bitmapWidth:    uint32(width),  // uint32
		bitmapHeight:   uint32(height), // uint32
		colorPlanes:    1,              // uint16
		BitsPerPixel:   uint16(bit),    // uint16
		markerBI_RGB:   0,              // uint32
		originalSize:   uint32(size),   // uint32
		printResH:      2835,           // uint32
		printResV:      2835,           // uint32
		Palette:        uint32(p),      // uint32
		importantColor: uint32(i),      // uint32
	}
}

// HeaderToBytes 将DIB的头结构转换为字节切片
func (dh *dibHeader) HeaderToBytes() []byte {
	d := make([]byte, dibHeaderSize)
	binary.LittleEndian.PutUint32(d[0:4], dh.dibSize)
	binary.LittleEndian.PutUint32(d[4:8], dh.bitmapWidth)
	binary.LittleEndian.PutUint32(d[8:12], dh.bitmapHeight)
	binary.LittleEndian.PutUint16(d[12:14], dh.colorPlanes)
	binary.LittleEndian.PutUint16(d[14:16], dh.BitsPerPixel)
	binary.LittleEndian.PutUint32(d[16:20], dh.markerBI_RGB)
	binary.LittleEndian.PutUint32(d[20:24], dh.originalSize)
	binary.LittleEndian.PutUint32(d[24:28], dh.printResH)
	binary.LittleEndian.PutUint32(d[28:32], dh.printResV)
	binary.LittleEndian.PutUint32(d[32:36], dh.Palette)
	binary.LittleEndian.PutUint32(d[36:40], dh.importantColor)
	return d
}

// EditDIBHeader 修改DIB头
func (dh *dibHeader) EditDIBHeader(b []byte) error {
	if len(b) <= 40 {
		return ErrIconsIndex
	}
	h := dh.HeaderToBytes()
	for i := 0; i < 40; i++ {
		b[i] = h[i]
	}
	return nil
}

func CreateWinIcon(b [][]byte) (*WinIcon, error) {

	return nil, nil
}

func getPerm() os.FileMode {
	if runtime.GOOS == "windows" {
		return 0
	} else {
		return 0666
	}
}
